package com.gc.ip.util;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.gc.ip.IPAddress;
import com.gc.ip.IPType;
import com.gc.ip.IPUtils;

public class IPAddressParser
{
	private static Log log = LogFactory.getLog(IPAddressParser.class);

	/**
	 * Parse a source string to an IPAddressParseResult
	 * @param source
	 * @param hasPrefixLength
	 * @param ipType
	 * @param result
	 */
	public static void parse(String source, boolean hasPrefixLength, IPType ipType,
		IPAddressParseResult result)
	{
		// empty input
		if(source == null || source.trim().isEmpty())
		{
			// set error type to EMPTY_INPUT, and return null
			result.setErrorType(IPAddressParseResult.ErrorType.EMPTY_INPUT);
			result.setIPAddress(new IPAddress(source));
			return;
		}
		
		result.setIPAddress(new IPAddress());
		if(hasPrefixLength)
		{
			parseIPWithMask(source, ipType, result);
		}
		else
		{
			parseIPWithoutMask(source, ipType, result);
		}
		//Reset the IP address if the parse result is NOT OK
		if(result.getErrorType() != IPAddressParseResult.ErrorType.OK)
		{
			result.setIPAddress(new IPAddress(source));
		}
	}
	
	/**
	 * Convenient method to convert a textual format ip address to an
	 * InetAddress
	 * 
	 * @param source
	 * @param hasPrefixLength
	 * @return
	 */
	public static InetAddress parse(String source, boolean hasPrefixLength, IPType ipType)
	{
		IPAddressParseResult result = new IPAddressParseResult();
		parse(source, hasPrefixLength, ipType, result);

		if(result.getErrorType() == IPAddressParseResult.ErrorType.OK)
		{
			byte[] addr = result.getIPAddress().getBytesAddr();
			try
			{
				InetAddress inetAddr = InetAddress.getByAddress(addr);
				return inetAddr;
			}
			catch(UnknownHostException e)
			{
				log.error("Failed to get InetAddress. addr = " + IPUtils.toIPString(addr), e);
			}
		}
		return null;
	}
	
	private static void parseIPWithoutMask(String source, IPType ipType, IPAddressParseResult result)
	{
		// Check if the ipAddress valid
		int intIPType = ((ipType == null) ? IPUtils.IP_TYPE_NONE : ipType.enumToInt());
		byte[] addr = IPUtils.toIPBytes(source, intIPType);
		if(addr == null)
		{
			if(log.isTraceEnabled())
			{
				log.trace("Convert IP to bytes failed. ipAddress=" + source + ", ipType=" + ipType);
			}
			result.setErrorType(IPAddressParseResult.ErrorType.INVALID_ADDRESS);
			return;
		}

		// Set the parsed ip address
		result.getIPAddress().setStrAddr(source);
		
		// Everything is OK.
		result.setErrorType(IPAddressParseResult.ErrorType.OK);
	}

	private static void parseIPWithMask(String source, IPType ipType, IPAddressParseResult result)
	{
		int slashIndex = source.indexOf('/');
		
		//not exist or the last character
		if(slashIndex < 0 || slashIndex == (source.length() - 1))
		{
			if(log.isTraceEnabled())
			{
				log.trace("Invalid prefix length: slashIndex=" + slashIndex);
			}
			result.setErrorType(IPAddressParseResult.ErrorType.INVALID_PREFIX);
			return;
		}
		
		//ip part
		String ipPart = source.substring(0, slashIndex).trim();
		//ipPart is empty
		if(ipPart.isEmpty())
		{
			if(log.isTraceEnabled())
			{
				log.trace("The ipPart is empty. ipAddress=" + source);
			}
			result.setErrorType(IPAddressParseResult.ErrorType.INVALID_ADDRESS);
			return;
		}
		
		// Check if the ipAddress valid
		int intIPType = ((ipType == null) ? IPUtils.IP_TYPE_NONE : ipType.enumToInt());
		byte[] bytesAddr = IPUtils.toIPBytes(ipPart, intIPType);
		if(bytesAddr == null)
		{
			if(log.isTraceEnabled())
			{
				log.trace("Convert IP to bytes failed. ipAddress=" + ipPart + ", ipType=" + ipType);
			}
			result.setErrorType(IPAddressParseResult.ErrorType.INVALID_ADDRESS);
			return;
		}
		result.getIPAddress().setStrAddr(ipPart);
		
		//prefix length part
		String plPart = source.substring(slashIndex + 1);
		int prefixLength = setPrefixLength(result.getIPAddress(), plPart);
		if(prefixLength < 0)
		{
			if(log.isTraceEnabled())
			{
				log.trace("Invalid prefix length. plPart=" + plPart + ", ipType=" + ipType);
			}
			result.setErrorType(IPAddressParseResult.ErrorType.INVALID_PREFIX);
			return;
		}
		
		// Don't forget to check the prefix length
		if(checkPrefixLength(result))
		{
			result.setErrorType(IPAddressParseResult.ErrorType.OK);
		}
	}
	
	private static int setPrefixLength(IPAddress ipAddress, String strPrefixLength)
	{
		int prefixLength = -1;

		// first check if it's a number
		try
		{
			prefixLength = Integer.parseInt(strPrefixLength);
		}
		catch(NumberFormatException e)
		{
			// not a valid number
			prefixLength = -1;
		}
		
		if(prefixLength >= 0)
		{
			ipAddress.setPrefixLength(prefixLength);
		}
		// then check if it's a subnet mask
		else
		{
			ipAddress.setSubnetMask(strPrefixLength);
			prefixLength = ipAddress.getPrefixLength();			
		}
		
		return prefixLength;
	}

	/**
	 * Check if the prefix length is in the valid range
	 * 
	 * @param result
	 * @return
	 */
	private static boolean checkPrefixLength(IPAddressParseResult result)
	{
		IPAddress ipAddress = result.getIPAddress();
		
		IPType ipType = ipAddress.getIPType();
		if(ipType == IPType.IPv4 || ipType == IPType.IPv6)
		{
			return true;
		}

		// Prefix Length is out of range
		result.setErrorType(IPAddressParseResult.ErrorType.INVALID_PREFIX);

		if(log.isDebugEnabled())
		{
			log.debug("checkPrefixLength failed. prefixLength=" + ipAddress.getPrefixLength()
				+ ", ipType=" + ipAddress.getIPType());
		}
		return false;
	}
}
